//
//  NSObject+KVO.m
//  KVO
//
//  Created by eport on 2021/1/9.
//

#import "NSObject+KVO.h"
#import <objc/runtime.h>
#import <objc/message.h>

NSString *const kPGKVOClassPrefix = @"PGKVOClassPrefix_";
NSString *const kPGKVOAssociatedObservers = @"PGKVOAssociatedObservers";

@interface PGObservationInfo : NSObject
@property(nonatomic,weak)NSObject *observer;
@property(nonatomic,copy)NSString *key;
@property(nonatomic,copy)WXoberserBlock block;

@end

@implementation PGObservationInfo

- (instancetype)initWithObserver:(NSObject *)observer key:(NSString *)key block:(WXoberserBlock)block
{
    self = [super init];
    if (self) {
        _observer = observer;
        _key = key;
        _block = block;
    }
    return self;
}

@end



static NSString * getterForSetter(NSString *setter)
{
    if (setter.length <=0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) {
        return nil;
    }
    
    // remove 'set' at the begining and ':' at the end
    NSRange range = NSMakeRange(3, setter.length - 4);
    NSString *key = [setter substringWithRange:range];
    
    // lower case the first letter
    NSString *firstLetter = [[key substringToIndex:1] lowercaseString];
    key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1)
                                       withString:firstLetter];
    
    return key;
}

static void kvo_setter(id self,SEL _cmd,id newValue){
    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = getterForSetter(setterName);
    if (!getterName) {
        NSString *reason = [NSString stringWithFormat:@"Object %@ does not have setter %@", self, setterName];
        @throw [NSException exceptionWithName:NSInvalidArgumentException
                                       reason:reason
                                     userInfo:nil];
        return;
    }
    
   
    
    id oldValue = [self valueForKey:getterName];
    
    struct objc_super superClazz = {
        .receiver = self,
        .super_class= class_getSuperclass(object_getClass(self))
    };
    
    void (*objc_msgSendSuperCasted)(void *,SEL,id) = (void *)objc_msgSendSuper;
    
    objc_msgSendSuperCasted(&superClazz,_cmd,newValue);
    
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
    
    for (PGObservationInfo *each in observers) {
        if ([each.key isEqualToString:getterName]) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                each.block(self,getterName,oldValue,newValue);
            });
        }
    }

    
    
    
}


static NSString *setterForGetter(NSString *getter){
    if (getter.length == 0) {
        return nil;
    }
    
    NSString *firstLetter = [[getter substringToIndex:1] uppercaseString];
    NSString *remainLetter = [getter substringFromIndex:1];
    NSString *setter = [NSString stringWithFormat:@"set%@%@:",firstLetter,remainLetter];
    return setter;
}


static NSArray *ClassMethodNames(Class c){
 
    NSMutableArray *array = [NSMutableArray array];
    
    unsigned int methodCount = 0;
    
    Method *methodList = class_copyMethodList(c, &methodCount);
    
    unsigned int i;
    for (i = 0; i < methodCount; i++) {
        [array addObject:NSStringFromSelector(method_getName(methodList[i]))];
    }
    free(methodList);
    return  array;
}

static Class kvo_class(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}

@implementation NSObject (KVO)


-(void)wx_addObserver:(NSObject *)observer
              keyPath:(NSString *)keyPath
            withBlock:(WXoberserBlock)block{
    
    SEL setterSelector = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSelector);
    if (!setterSelector) {
        NSString *reason = [NSString stringWithFormat:@"Object %@ does not have a setter for key %@",self,keyPath];
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil];
        
        return;
    }
    
    Class clazz = object_getClass(self);
    NSString *clazzName = NSStringFromClass(clazz);
    
    if (![clazzName hasPrefix:kPGKVOClassPrefix]) {
        clazz = [self makeKvoClassWithOriginalClassName:clazzName];
        //设置isa指向的Class
        object_setClass(self, clazz);
    }
    
    if (![self hasSelector:setterSelector]) {
        const char *types = method_getTypeEncoding(setterMethod);
        class_addMethod(clazz,setterSelector , (IMP)kvo_setter, types);
    }
    
                PGObservationInfo *info = [[PGObservationInfo alloc]initWithObserver:observer key:keyPath block:block];
            
                NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
                if (!observers) {
                    observers = [NSMutableArray array];
                    objc_setAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
                }
                [observers addObject:info];
                
                
    
    
    
}

-(Class)makeKvoClassWithOriginalClassName:(NSString *)originalClassName{
    NSString *kvoClazzName = [kPGKVOClassPrefix stringByAppendingString:originalClassName];
    Class clazz = NSClassFromString(kvoClazzName);
    if (clazz) {
        return clazz;
    }
    //class doesnt exist yet,make it
    Class originalClazz = object_getClass(self);
    //动态创建一个类
    Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);
    
    Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
    
    const char *types = method_getTypeEncoding(clazzMethod);

    class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class,types);
    objc_registerClassPair(kvoClazz);
    return kvoClazz;
    
}


-(BOOL)hasSelector:(SEL)Selector{
    Class clazz = object_getClass(self);
    unsigned int methodCount = 0;
    Method *methodList = class_copyMethodList(clazz, &methodCount);
    
    for (unsigned int i = 0; i < methodCount; i++) {
        SEL thisSelector = method_getName(methodList[i]);
        if (thisSelector == Selector) {
            free(methodList);
            return YES;
        }
    }
    free(methodList);
    return NO;
}


    
-(void)wx_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
        
    NSMutableArray* observers = objc_getAssociatedObject(self, (__bridge const void *)(kPGKVOAssociatedObservers));
    
    PGObservationInfo *infoToRemove;
    for (PGObservationInfo* info in observers) {
        if (info.observer == observer && [info.key isEqual:keyPath]) {
            infoToRemove = info;
            break;
        }
    }
    
    [observers removeObject:infoToRemove];
        

}
    

@end
